<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TradingView K线图表测试</title>
    <script src="https://cdn.jsdelivr.net/npm/lightweight-charts@4.1.3/dist/lightweight-charts.standalone.production.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f0f2f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .header {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        h1 {
            margin: 0 0 20px 0;
            color: #333;
        }
        .controls {
            display: flex;
            gap: 15px;
            align-items: center;
            flex-wrap: wrap;
        }
        .control-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        label {
            font-size: 14px;
            color: #666;
        }
        select, button {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        button {
            background-color: #1890ff;
            color: white;
            border: none;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #40a9ff;
        }
        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
        .chart-container {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            height: 600px;
            margin-bottom: 20px;
        }
        .status {
            background-color: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        .status-connected {
            color: #52c41a;
        }
        .status-disconnected {
            color: #ff4d4f;
        }
        .logs {
            background-color: #2c3e50;
            color: #ecf0f1;
            padding: 15px;
            border-radius: 8px;
            max-height: 300px;
            overflow-y: auto;
            font-family: 'Courier New', monospace;
            font-size: 14px;
        }
        .log-item {
            margin-bottom: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>TradingView K线图表测试</h1>
            <div class="controls">
                <div class="control-group">
                    <label for="ws-url">WebSocket URL</label>
                    <input type="text" id="ws-url" value="ws://localhost:8080/ws" style="padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
                </div>
                <div class="control-group">
                    <label for="symbol">交易对</label>
                    <select id="symbol">
                        <option value="BTCUSDT">BTC/USDT</option>
                        <option value="ETHUSDT">ETH/USDT</option>
                        <option value="BNBUSDT">BNB/USDT</option>
                    </select>
                </div>
                <div class="control-group">
                    <label for="interval">K线周期</label>
                    <select id="interval">
                        <option value="1m">1分钟</option>
                        <option value="5m">5分钟</option>
                        <option value="15m">15分钟</option>
                        <option value="1h">1小时</option>
                        <option value="4h">4小时</option>
                        <option value="1d">1天</option>
                    </select>
                </div>
                <button id="connect-btn">连接</button>
                <button id="disconnect-btn" disabled>断开连接</button>
                <button id="subscribe-btn" disabled>订阅</button>
                <button id="unsubscribe-btn" disabled>取消订阅</button>
            </div>
        </div>
        
        <div id="status" class="status status-disconnected">
            状态: 未连接到WebSocket服务器
        </div>
        
        <div id="chart-container" class="chart-container"></div>
        
        <div class="logs" id="logs">
            <div class="log-item">[日志] 页面已加载，请连接WebSocket服务器</div>
        </div>
    </div>

    <script>
        // DOM 元素
        const wsUrlInput = document.getElementById('ws-url');
        const symbolSelect = document.getElementById('symbol');
        const intervalSelect = document.getElementById('interval');
        const connectBtn = document.getElementById('connect-btn');
        const disconnectBtn = document.getElementById('disconnect-btn');
        const subscribeBtn = document.getElementById('subscribe-btn');
        const unsubscribeBtn = document.getElementById('unsubscribe-btn');
        const statusElement = document.getElementById('status');
        const logsElement = document.getElementById('logs');
        const chartContainer = document.getElementById('chart-container');
        
        // WebSocket 对象
        let socket = null;
        let isSubscribed = false;
        let currentChannel = '';
        
        // 图表对象
        let chart = null;
        let candlestickSeries = null;
        let dataPoints = [];
        let lastTimestamp = null;
        
        // 添加日志
        function addLog(message) {
            const timestamp = new Date().toLocaleTimeString();
            const logItem = document.createElement('div');
            logItem.className = 'log-item';
            logItem.textContent = `[${timestamp}] ${message}`;
            logsElement.appendChild(logItem);
            logsElement.scrollTop = logsElement.scrollHeight;
        }
        
        // 更新状态
        function updateStatus(isConnected) {
            if (isConnected) {
                statusElement.className = 'status status-connected';
                statusElement.textContent = '状态: 已连接到WebSocket服务器';
                connectBtn.disabled = true;
                disconnectBtn.disabled = false;
                subscribeBtn.disabled = false;
                addLog('WebSocket连接已建立');
            } else {
                statusElement.className = 'status status-disconnected';
                statusElement.textContent = '状态: 未连接到WebSocket服务器';
                connectBtn.disabled = false;
                disconnectBtn.disabled = true;
                subscribeBtn.disabled = true;
                unsubscribeBtn.disabled = true;
                isSubscribed = false;
                currentChannel = '';
                addLog('WebSocket连接已断开');
            }
        }
        
        // 初始化图表
        function initChart() {
            // 清除之前的图表
            if (chart) {
                chart.remove();
            }
            
            // 创建新图表
            chart = LightweightCharts.createChart(chartContainer, {
                width: chartContainer.clientWidth,
                height: chartContainer.clientHeight,
                layout: {
                    backgroundColor: '#ffffff',
                    textColor: '#333333',
                },
                grid: {
                    vertLines: {
                        color: '#e9e9e9',
                    },
                    horzLines: {
                        color: '#e9e9e9',
                    },
                },
                priceScale: {
                    borderColor: '#cccccc',
                },
                timeScale: {
                    borderColor: '#cccccc',
                    timeVisible: true,
                    secondsVisible: false,
                },
                rightPriceScale: {
                    scaleMargins: {
                        top: 0.3,
                        bottom: 0.3,
                    },
                },
                crosshair: {
                    mode: LightweightCharts.CrosshairMode.Normal,
                },
                localization: {
                    locale: 'zh-CN',
                    dateFormat: 'yyyy-MM-dd',
                },
            });
            
            // 创建K线系列
            candlestickSeries = chart.addCandlestickSeries({
                upColor: '#26a69a',
                downColor: '#ef5350',
                borderVisible: true,
                wickVisible: true,
                borderColor: '#26a69a',
                wickColor: '#757575',
            });
            
            // 添加成交量指标
            const volumeSeries = chart.addHistogramSeries({
                color: '#26a69a',
                lineWidth: 2,
                priceFormat: {
                    type: 'volume',
                },
                scaleMargins: {
                    top: 0.8, // 给K线图留出足够空间
                    bottom: 0,
                },
            });
            
            // 重置数据点
            dataPoints = [];
            lastTimestamp = null;
            
            // 窗口大小改变时调整图表大小
            window.addEventListener('resize', () => {
                chart.resize(chartContainer.clientWidth, chartContainer.clientHeight);
            });
            
            addLog('图表已初始化');
        }
        
        // 处理K线数据
        function handleKlineData(data) {
            try {
                let klineData;
                
                // 检查data类型，如果是字符串则解析，否则直接使用
                if (typeof data === 'string') {
                    klineData = JSON.parse(data);
                } else if (typeof data === 'object' && data !== null) {
                    klineData = data;
                } else {
                    addLog(`无效的K线数据类型: ${typeof data}`);
                    return;
                }
                
                // 检查是否有K线数据
                if (!klineData.k) {
                    addLog('无效的K线数据: 缺少k字段');
                    return;
                }
                
                const k = klineData.k;
                
                // 转换为TradingView需要的格式
                const timestamp = new Date(k.t).getTime(); // 转换为毫秒时间戳
                const candle = {
                    time: timestamp / 1000, // TradingView需要的是秒级时间戳
                    open: parseFloat(k.o),
                    high: parseFloat(k.h),
                    low: parseFloat(k.l),
                    close: parseFloat(k.c),
                    volume: parseFloat(k.v),
                };
                
                // 检查是否是新的K线
                if (!lastTimestamp || timestamp > lastTimestamp) {
                    dataPoints.push(candle);
                    lastTimestamp = timestamp;
                    
                    // 限制数据点数量，防止图表性能问题
                    if (dataPoints.length > 1000) {
                        dataPoints.shift();
                    }
                    
                    // 更新图表
                    const candlesticks = dataPoints.map(d => ({
                        time: d.time,
                        open: d.open,
                        high: d.high,
                        low: d.l,
                        close: d.close,
                    }));
                    
                    const volumes = dataPoints.map(d => ({
                        time: d.time,
                        value: d.volume,
                        color: d.close >= d.open ? '#26a69a' : '#ef5350',
                    }));
                    
                    // 更新K线图和成交量
                    candlestickSeries.setData(candlesticks);
                    
                    // 自动滚动到最新数据
                    chart.timeScale().fitContent();
                    
                    addLog(`更新K线数据: ${k.s} ${k.i} - 开盘: ${k.o}, 最高: ${k.h}, 最低: ${k.l}, 收盘: ${k.c}`);
                }
            } catch (error) {
                addLog(`处理K线数据错误: ${error.message}`);
            }
        }
        
        // 连接WebSocket
        function connect() {
            const url = wsUrlInput.value;
            
            try {
                // 初始化图表
                initChart();
                
                // 创建WebSocket连接
                socket = new WebSocket(url);
                
                socket.onopen = function() {
                    updateStatus(true);
                };
                
                socket.onmessage = function(event) {
                    try {
                        // 解析接收到的消息
                        // 先检查数据是否为空或不是字符串
                        if (!event.data || typeof event.data !== 'string') {
                            addLog(`接收到无效的消息数据类型: ${typeof event.data}`);
                            return;
                        }
                        
                        // 尝试解析JSON，处理可能的格式问题
                        let message;
                        try {
                            message = JSON.parse(event.data);
                        } catch (jsonError) {
                            // 尝试修复可能的JSON格式问题（如尾部多余字符）
                            const cleanedData = event.data.trim();
                            if (cleanedData !== event.data) {
                                try {
                                    message = JSON.parse(cleanedData);
                                    addLog('已修复并解析了格式不规范的JSON消息');
                                } catch (secondError) {
                                    addLog(`解析消息错误: ${jsonError.message}，尝试清理后仍失败: ${secondError.message}`);
                                    return;
                                }
                            } else {
                                addLog(`解析消息错误: ${jsonError.message}`);
                                return;
                            }
                        }
                        
                        // 确保message是对象
                        if (typeof message !== 'object' || message === null) {
                            addLog(`接收到的消息不是有效的对象: ${typeof message}`);
                            return;
                        }
                        
                        // 处理发布的消息
                        if (message.type === 'publish' && message.channel) {
                            // 检查是否是K线数据频道
                            if (message.channel.startsWith('kline_')) {
                                handleKlineData(message.data);
                            }
                        } else if (message.type === 'ping') {
                            // 处理ping消息
                            try {
                                const pongMessage = JSON.stringify({ type: 'pong' });
                                socket.send(pongMessage);
                                addLog('已发送pong响应');
                            } catch (pongError) {
                                addLog(`发送pong响应时出错: ${pongError.message}`);
                            }
                        }
                    } catch (error) {
                        addLog(`处理WebSocket消息时发生错误: ${error.message}`);
                    }
                };
                
                socket.onclose = function(event) {
                    // 记录断开原因和代码
                    const code = event.code;
                    const reason = event.reason;
                    const wasClean = event.wasClean;
                    
                    let closeReason = `WebSocket连接已断开`;
                    if (code) closeReason += ` (代码: ${code})`;
                    if (reason) closeReason += ` (原因: ${reason})`;
                    if (!wasClean) closeReason += ` (非正常关闭)`;
                    
                    // 添加常见断开代码的解释
                    if (code === 1000) {
                        closeReason += ` - 正常关闭`;
                    } else if (code === 1001) {
                        closeReason += ` - 服务器端关闭`;
                    } else if (code === 1006) {
                        closeReason += ` - 连接异常断开（可能是网络问题或服务器崩溃）`;
                    } else if (code === 1011) {
                        closeReason += ` - 服务器内部错误`;
                    } else if (code >= 1000 && code < 2000) {
                        closeReason += ` - 标准关闭代码`;
                    }
                    
                    addLog(closeReason);
                    updateStatus(false);
                };
                
                socket.onerror = function(error) {
                    addLog(`WebSocket错误: ${error.message || '未知错误'}`);
                };
            } catch (error) {
                addLog(`连接失败: ${error.message}`);
            }
        }
        
        // 断开连接
        function disconnect() {
            if (socket) {
                socket.close();
                socket = null;
            }
        }
        
        // 订阅频道
        function subscribe() {
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                addLog('WebSocket未连接');
                return;
            }
            
            // 获取当前选择的交易对和周期
            const symbol = symbolSelect.value;
            const interval = intervalSelect.value;
            
            // 构建频道名称
            currentChannel = `kline_${symbol}_${interval}`;
            
            // 发送订阅请求
            const message = JSON.stringify({
                type: 'subscribe',
                channel: currentChannel
            });
            
            socket.send(message);
            isSubscribed = true;
            subscribeBtn.disabled = true;
            unsubscribeBtn.disabled = false;
            
            addLog(`已订阅频道: ${currentChannel}`);
        }
        
        // 取消订阅
        function unsubscribe() {
            if (!socket || socket.readyState !== WebSocket.OPEN || !currentChannel) {
                addLog('无法取消订阅');
                return;
            }
            
            // 发送取消订阅请求
            const message = JSON.stringify({
                type: 'unsubscribe',
                channel: currentChannel
            });
            
            socket.send(message);
            isSubscribed = false;
            subscribeBtn.disabled = false;
            unsubscribeBtn.disabled = true;
            
            addLog(`已取消订阅频道: ${currentChannel}`);
        }
        
        // 事件监听
        connectBtn.addEventListener('click', connect);
        disconnectBtn.addEventListener('click', disconnect);
        subscribeBtn.addEventListener('click', subscribe);
        unsubscribeBtn.addEventListener('click', unsubscribe);
        
        // 选择新的交易对或周期时，需要重新订阅
        symbolSelect.addEventListener('change', function() {
            if (isSubscribed) {
                unsubscribe();
            }
        });
        
        intervalSelect.addEventListener('change', function() {
            if (isSubscribed) {
                unsubscribe();
            }
        });
        
        // 页面加载完成后自动初始化图表
        window.addEventListener('load', function() {
            addLog('TradingView K线图表测试页面已加载');
        });
    </script>
</body>
</html>